////////////////////////////////////////////////////////////////////////////////
//
//  Copyright (C) 2003-2006 Adobe Macromedia Software LLC and its licensors.
//  All Rights Reserved. The following is Source Code and is subject to all
//  restrictions on such code as contained in the End User License Agreement
//  accompanying this product.
//
////////////////////////////////////////////////////////////////////////////////

package mx.charts.series
{

import flash.display.DisplayObject;
import flash.display.Graphics;
import flash.geom.Rectangle;
import mx.charts.HitData;
import mx.charts.chartClasses.DataDescription;
import mx.charts.chartClasses.InstanceCache;
import mx.charts.chartClasses.LegendData;
import mx.charts.chartClasses.Series;
import mx.charts.renderers.LineRenderer;
import mx.charts.series.items.LineSeriesItem;
import mx.charts.series.items.LineSeriesSegment;
import mx.charts.series.renderData.LineSeriesRenderData;
import mx.charts.styles.HaloDefaults;
import mx.collections.CursorBookmark;
import mx.core.ClassFactory;
import mx.core.IDataRenderer;
import mx.core.IFactory;
import mx.core.IFlexDisplayObject;
import mx.core.mx_internal;
import mx.graphics.IFill;
import mx.graphics.IStroke;
import mx.graphics.LinearGradientStroke;
import mx.graphics.SolidColor;
import mx.graphics.Stroke;
import mx.skins.ProgrammaticSkin;
import mx.styles.CSSStyleDeclaration;
import mx.styles.ISimpleStyleClient;
import mx.charts.chartClasses.BoundedValue;
import mx.charts.chartClasses.CartesianTransform;

include "../styles/metadata/FillStrokeStyles.as"
include "../styles/metadata/ItemRendererStyles.as"

/**
 *  Specifies the line type for the chart.
 *  Possible values are:
 *  <ul>
 *    <li><code>"curve"</code>:
 *    Draws curves between data points.</li>
 *    
 *    <li><code>"horizontal"</code>:
 *    Draws only the vertical line from the x-coordinate
 *    of the first point to the x-coordinate of the second point
 *    at the y-coordinate of the second point.
 *    Repeats this for each data point.</li>
 *    
 *    <li><code>"vertical"</code>:
 *    Draws only the vertical line from the y-coordinate
 *    of the first point to the y-coordinate of the second point
 *    at the x-coordinate of the second point.
 *    Repeats this for each data point.</li>
 *    
 *    <li><code>"segment"</code>:
 *    Draws lines as connected segments that are angled
 *    to connect at each data point in the series.</li>
 *    
 *    <li><code>"step"</code>:
 *    Draws lines as horizontal segments.
 *    At the first data point, draws a horizontal line
 *    and then a vertical line to the second point,
 *    and repeats for each data point.</li>
 *    
 *    <li><code>"reverseStep"</code>:L
 *    Draws lines as horizontal segments.
 *    At the first data point, draws a vertical line
 *    and then a horizontal line to the second point,
 *    and repeats for each data point.</li>
 *  </ul>
 *  The default is <code>"segment"</code>.
 */
[Style(name="form", type="String", enumeration="segment,step,reverseStep,vertical,horizontal,curve", inherit="no")]

/**
 *  A factory that represents the class the series uses
 *  to represent the individual line segments in the series.
 *  This class is instantiated once
 *  for each distinct segment of the series.
 *	Classes used as lineSegmentRenderers should implement
 *  the IFlexDisplayObject, ISimpleStyleClient, and IDataRenderer interfaces.
 *  The <code>data</code> property is assigned an instance of
 *  mx.charts.series.items.LineSeriesSegment
 *	that describes the segment to render.
 */
[Style(name="lineSegmentRenderer", type="mx.core.IFactory", inherit="no")]

/** 
  *  Sets the stroke for the actual line segments. 
  *  The default value for a LineChart control is orange (<code>0xE47801</code>). The default color for a LineSeries used in a CartesianChart control is black (<code>0x000000</code>). The default value for the width is 3.
  */
[Style(name="lineStroke", type="mx.graphics.IStroke", inherit="no")]

/** 
 *  Specifies the radius, in pixels, of the chart elements for the data points.
 *  This property applies only if you specify an item renderer
 *  using the <code>itemRenderer</code> property.  
 *  You can specify the <code>itemRenderer</code> in MXML or using styles.  
 *  
 *  @default 0 
 */
[Style(name="radius", type="Number", format="Length", inherit="no")]

/**
 *  Defines a data series for a LineChart control.
 *  By default, this class uses the ShadowLineRenderer class. 
 *  Optionally, you can define an itemRenderer for the data series.
 *  The itemRenderer must implement the ILineRenderer interface. 
 *
 *  @mxml
 *  
 *  <p>The <code>&lt;mx:LineSeries&gt;</code> tag inherits all the properties
 *  of its parent classes and adds the following properties:</p>
 *  
 *  <pre>
 *  &lt;mx:LineSeries
 *    <strong>Properties</strong>
 *    form="segment|curve|horizontal|reverseStep|step|vertical"
 *    interpolateValues="false|true"
 *    sortOnXField="false|true"
 *    xField="null"
 *    yField="null"
 * 
 *    <strong>Styles</strong>
 *    fill="0xFFFFFF"
 *    form="segment|curve|horizontal|reverseStep|step|vertical"
 *    itemRenderer="<i>itemRenderer</i>"
 *    legendMarkerRenderer="<i>Defaults to series's itemRenderer</i>"
 *    lineSegmentRenderer="<i>ShadowLineRenderer</i>"
 *    lineStroke="Stroke(0xE47801,3)"
 *    radius="0"
 *    stroke="<i>IStroke; no default</i>" 
 *  /&gt;
 *  </pre>
 *  
 *  @see mx.charts.LineChart
 *  
 *  @includeExample ../examples/Line_AreaChartExample.mxml
 */
public class LineSeries extends Series
{
    include "../../core/Version.as";

	//--------------------------------------------------------------------------
	//
	//  Class initialization
	//
	//--------------------------------------------------------------------------

	/**
	 *  @private
	 */
	private static var stylesInited:Boolean = initStyles();	

	/**
	 *  @private
	 */
	private static function initStyles():Boolean
	{
		HaloDefaults.init();

		var seriesStyle:CSSStyleDeclaration =
			HaloDefaults.createSelector("LineSeries");		

		seriesStyle.defaultFactory = function():void
		{
			this.fill = new SolidColor(0xFFFFFF);
			this.lineSegmentRenderer = new ClassFactory(LineRenderer);
			this.lineStroke = new Stroke(0, 3);
			this.radius = 4;
		}

		return true;
	}

	//--------------------------------------------------------------------------
	//
	//  Constructor
	//
	//--------------------------------------------------------------------------
	
	/**
	 *  Constructor.
	 */
	public function LineSeries()
	{
		super();

		_pointInstanceCache = new InstanceCache(null, this, 1000);
		_pointInstanceCache.creationCallback = applyItemRendererProperties;
		
		_segmentInstanceCache = new InstanceCache(null, this, 0);
		_segmentInstanceCache.properties = { styleName: this };
	}

	//--------------------------------------------------------------------------
	//
	//  Variables
	//
	//--------------------------------------------------------------------------
	
	/**
	 *  @private
	 */
	private var _pointInstanceCache:InstanceCache;
	
	/**
	 *  @private
	 */
	private var _renderData:LineSeriesRenderData;	

	/**
	 *  @private
	 */
	private var _segmentInstanceCache:InstanceCache;
	
	//--------------------------------------------------------------------------
	//
	//  Overridden properties
	//
	//--------------------------------------------------------------------------

    //----------------------------------
	//  legendData
    //----------------------------------

	/**
	 *  @private
	 */
	override public function get legendData():Array
	{
		var radius:Number = getStyle("radius")
		var itemRenderer:IFactory = getStyle("itemRenderer");

		var markerAspectRatio:Number;
		var color:int = 0;
		var marker:IFlexDisplayObject;
		
		var ld:LegendData = new LegendData();
		ld.element = this;
		ld.label = displayName;	
		
		
		var markerFactory:IFactory = getStyle("legendMarkerRenderer");
		if (markerFactory != null)
		{
			marker = markerFactory.newInstance();
			if (marker is ISimpleStyleClient)
				(marker as ISimpleStyleClient).styleName = this;
			ld.aspectRatio = 1;
		}
		else if (itemRenderer == null || radius == 0 || isNaN(radius))
		{
			marker = new LineSeriesLegendMarker(this);			
		}
		else
		{
			markerFactory = getStyle("itemRenderer");
			marker = markerFactory.newInstance();
			ld.aspectRatio = 1;
			if (marker as ISimpleStyleClient)
				(marker as ISimpleStyleClient).styleName = this;
		}

		ld.marker = marker;
		
		return [ld];
	}

    //----------------------------------
	//  renderData
    //----------------------------------

	/**
	 *  @private
	 */
	override protected function get renderData():Object
	{
		if (_renderData == null ||
			_renderData.cache == null ||
			_renderData.cache.length == 0)
		{
			var renderDataType:Class = this.renderDataType;
			var ld:LineSeriesRenderData = new renderDataType();
			ld.cache = ld.filteredCache = [];
			ld.segments = [];
			ld.radius = 0;
			return ld;
		}

		_renderData.radius = getStyle("radius");

		return _renderData;
	}
	
	//--------------------------------------------------------------------------
	//
	//  Properties
	//
	//--------------------------------------------------------------------------

    //----------------------------------
	//  form
    //----------------------------------

	[Inspectable(category="Styles", enumeration="segment,step,reverseStep,vertical,horizontal,curve", defaultValue="vertical")]

	/**
	 *  Specifies the line type for the chart.
	 *  Possible values are:<ul>
	 *    <li><code>"curve"</code>:
	 *    Draws curves between data points.</li>
	 *  
	 *    <li><code>"horizontal"</code>:
	 *    Draws only the vertical line from the x-coordinate
	 *    of the first point to the x-coordinate of the second point
	 *    at the y-coordinate of the second point.
	 *    Repeats this for each data point.</li>
	 *  
	 *    <li><code>"vertical"</code>:
	 *    Draws only the vertical line from the y-coordinate
	 *    of the first point to the y-coordinate of the second point
	 *    at the x-coordinate of the second point.
	 *    Repeats this for each data point.</li>
	 *  
	 *    <li><code>"segment"</code>:
	 *    Draws lines as connected segments that are angled
	 *    to connect at each data point in the series.</li>
	 *  
	 *    <li><code>"step"</code>:
	 *    Draws lines as horizontal segments.
	 *    At the first data point, draws a horizontal line
	 *    and then a vertical line to the second point,
	 *    and repeats for each data point.</li>
	 *  
	 *    <li><code>"reverseStep"</code>:
	 *    Draws lines as horizontal segments.
	 *    At the first data point, draws a vertical line
	 *    and then a horizontal line to the second point,
	 *    and repeats for each data point.</li>
	 *  </ul>
	 *  The default is <code>"segment"</code>. 
	 */
	public function get form():String
	{
		return getStyle("form");
	}
	
	/**
	 *  @private
	 */
	public function set form(value:String):void
	{
		setStyle("form", value);
	}

   //----------------------------------
	//  interpolateValues
    //----------------------------------

	/**
	 *  @private
	 *  Storage for the interpolateValues property.
	 */
	private var _interpolateValues:Boolean = false;	

    [Inspectable(category="General")]

	/** 
	 *  Specifies how to represent missing data.
	 *
	 *  <p>Set to <code>false</code> to break the line at the missing value.
	 *  Set to <code>true</code> to draw a continuous line by interpolating the missing value.</p>
	 *  
	 *  @default false
	*/
	public function get interpolateValues():Boolean
	{
		return _interpolateValues;
	}

	/**
	 *  @private
	 */
	public function set interpolateValues(value:Boolean):void
	{
		if (_interpolateValues != value)
		{
			_interpolateValues = value;

			invalidateData();
		}		
	}

    //----------------------------------
	//  items
    //----------------------------------

	/**
	 *  @private
	 */
	mx_internal function get items():Array
	{
		return _renderData ?
			   _renderData.filteredCache :
			   null;
	}

    //----------------------------------
	//  itemType
    //----------------------------------

	[Inspectable(environment="none")]

	/**
	 *  The subtype of ChartItem used by this series
	 *  to represent individual items.
	 *  Subclasses can override and return a more specialized class
	 *  if they need to store additional information in the items.
	 */
	protected function get itemType():Class
	{
		return LineSeriesItem;
	}
	
    //----------------------------------
	//  lineSegmentType
    //----------------------------------

	[Inspectable(environment="none")]

	/**
	 *  The class used by this series to store all data
	 *  necessary to represent a line segment.
	 *  Subclasses can override and return a more specialized class
	 *  if they need to store additional information for rendering.
	 */
	protected function get lineSegmentType():Class
	{
		return LineSeriesSegment;
	}

    //----------------------------------
	//  radius
    //----------------------------------

    [Inspectable(category="Styles")]

	/** 
	 *  Specifies the radius, in pixels, of the chart elements
	 *  for the data points.
	 *  This property applies only if you specify an item renderer
	 *  using the <code>itemRenderer</code> property.  
	 *  You can specify the <code>itemRenderer</code> in MXML or using styles.  
	 *  
	 *  @default 0
	 */
	public function get radius():Number
	{
		return getStyle("radius");
	}
	
	/**
	 *  @private
	 */
	public function set radius(value:Number):void
	{
		setStyle("radius", value);
	}

    //----------------------------------
	//  renderDataType
    //----------------------------------

	[Inspectable(environment="none")]

	/**
	 *  The subtype of ChartRenderData used by this series
	 *  to store all data necessary to render.
	 *  Subclasses can override and return a more specialized class
	 *  if they need to store additional information for rendering.
	 */
	protected function get renderDataType():Class
	{
		return LineSeriesRenderData;
	}
	
    //----------------------------------
	//  sortOnXField
    //----------------------------------

	/**
	 *  @private
	 *  Storage for the sortOnXField property.
	 */
	private var _sortOnXField:Boolean = true;

	[Inspectable]

	/** 
	 *  Requests the line datapoints be sorted from left to right
	 *  before rendering.
	 *
	 *  <p>By default, the LineSeries renders points from left to right.
	 *  Set this property to <code>false</code> to render the items
	 *  in the order they appear in the data provider.</p>
	 *
	 *  @default true
	 */
	public function get sortOnXField():Boolean
	{
		return _sortOnXField;
	}

	/**
	 *  @private
	 */
	public function set sortOnXField(value:Boolean):void 
	{
		if (_sortOnXField == value)
			return;
		_sortOnXField = value;

		invalidateMapping();
	}

    //----------------------------------
	//  xField
    //----------------------------------

	/**
	 *  @private
	 *  Storage for the xField property.
	 */
	private var _xField:String = "";
	
    [Inspectable(category="General")]

	/**
	 *  Specifies the field of the data provider
	 *  that determines the x-axis location of each data point. 
	 *  If <code>null</code>, the data points are rendered
	 *  in the order they appear in the data provider.
	 *
	 *  @default null
	 */
	public function get xField():String
	{
		return _xField;
	}
	
	/**
	 *  @private
	 */
	public function set xField(value:String):void
	{
		_xField = value;

		dataChanged();
	}

    //----------------------------------
	//  yField
    //----------------------------------

	/**
	 *  @private
	 *  Storage for the yField property.
	 */
	private var _yField:String = "";	
	
    [Inspectable(category="General")]

	/**
	 *  Specifies the field of the data provider
	 *  that determines the y-axis location of each data point. 
	 *  If <code>null</code>, the LineSeries assumes the data provider
	 *  is an Array of numbers, and uses the numbers as values. 
	 *
	 *  @default null
	 */
	public function get yField():String
	{
		return _yField;
	}
	
	/**
	 *  @private
	 */
	public function set yField(value:String):void
	{
		_yField = value;

		dataChanged();
	}
	
	//--------------------------------------------------------------------------
	//
	//  Overridden methods: UIComponent
	//
	//--------------------------------------------------------------------------

	/**
	 *  @private
	 */
	override protected function updateDisplayList(unscaledWidth:Number,
												  unscaledHeight:Number):void
	{
		super.updateDisplayList(unscaledWidth, unscaledHeight);
		
		var renderData:LineSeriesRenderData = (transitionRenderData != null)? LineSeriesRenderData(transitionRenderData) :_renderData; 
		if (renderData == null || renderData.filteredCache == null)
			return;
		
		var g:Graphics = graphics;
		
		g.clear();

		var radius:Number = getStyle("radius");		
		var sampleCount:int = renderData.filteredCache.length;
		var i:int;
			
		
		var segCount:int = renderData.segments.length;

	
		var activeRenderCache:Array;
		
		// figure out what cache we're rendering from. If there's a bounds based transtion underway, we need
		// to rebuild our cache from the bounds
		if (renderData == transitionRenderData && renderData.elementBounds != null)
		{
			var elementBounds:Array = renderData.elementBounds;
			sampleCount= elementBounds.length;
			activeRenderCache = renderData.filteredCache;


			for (i = 0; i < sampleCount; i++)
			{
				var rcBounds:Object = elementBounds[i];
				var localData:LineSeriesItem = activeRenderCache[i];
				localData.x = (rcBounds.left + rcBounds.right)/2;
				localData.y = (rcBounds.bottom + rcBounds.top)/2;
			}
		}
		else
		{
			activeRenderCache = renderData.filteredCache;
		}

		// now position each segment
		
		_segmentInstanceCache.factory = getStyle("lineSegmentRenderer");
		_segmentInstanceCache.count = segCount;
		var instances:Array = _segmentInstanceCache.instances;
		var v:LineSeriesItem;
		
		for (i = 0; i < segCount; i++)
		{
			var segment:IFlexDisplayObject = instances[i];
			if (segment is IDataRenderer)
				IDataRenderer(segment).data = renderData.segments[i];
			segment.setActualSize(unscaledWidth,unscaledHeight);
		}		
		
		
		// if the user has asked for markers at each datapoint, position those as well
		if (radius > 0)
		{
			_pointInstanceCache.factory = getStyle("itemRenderer");
			_pointInstanceCache.count = renderData.validPoints;			

			instances = _pointInstanceCache.instances;
			var nextInstanceIdx:int = 0;

			var bSetData:Boolean = (sampleCount > 0 && (instances[0] is IDataRenderer))

			var rc:Rectangle;
			var inst:IFlexDisplayObject;
			
			if (renderData == transitionRenderData && renderData.elementBounds != null)
			{
				for (i = 0; i < sampleCount; i++)
				{
					v = activeRenderCache[i];
					inst = instances[nextInstanceIdx++];
					v.itemRenderer = inst;
					if (inst != null)
					{
						if (bSetData)
							IDataRenderer(inst).data = v;
						rc = elementBounds[i];
						inst.move(rc.left,rc.top);
						inst.setActualSize(rc.width,rc.height);
					}
				}
			}
			else
			{
				for (i = 0; i < sampleCount; i++)
				{
					v  = activeRenderCache[i];
					var e:Object = renderData.filteredCache[i];
					if (filterData && (isNaN(e.x) || isNaN(e.y)))
						continue;

					inst = instances[nextInstanceIdx++];
					v.itemRenderer = inst;
					
					if (inst != null)
					{
						if (bSetData)
							IDataRenderer(inst).data = v;
						inst.move(v.x-radius,v.y - radius);
						inst.setActualSize(2*radius,2*radius);
					}
				}
			}				
		}
	}

	/**
	 *  @private
	 */
	override public function styleChanged(styleProp:String):void
	{
		super.styleChanged(styleProp);

		invalidateDisplayList();

		legendDataChanged();
	}

	//--------------------------------------------------------------------------
	//
	//  Overridden methods: ???
	//
	//--------------------------------------------------------------------------

	/**
	 *  @private
	 */
	override protected function updateData():void
	{
		var renderDataType:Class = this.renderDataType;
		_renderData= new renderDataType();

		_renderData.cache = [];
		if (dataProvider)
		{			
			cursor.seek(CursorBookmark.FIRST);
			var i:int = 0;
			var itemClass:Class = itemType;
			while (!cursor.afterLast)
			{
				_renderData.cache[i] = new itemClass(this,cursor.current,i);
				i++;
				cursor.moveNext();
			}

			cacheDefaultValues(_yField,_renderData.cache,"yValue");
			cacheIndexValues(_xField,_renderData.cache,"xValue");
		}

		_renderData.validPoints = _renderData.cache.length;
		super.updateData();
	}

	/**
	 *  @private
	 */
	override protected function updateMapping():void
	{
		dataTransform.getAxis(CartesianTransform.HORIZONTAL_AXIS).mapCache(_renderData.cache,"xValue","xNumber", (_xField == ""));
		dataTransform.getAxis(CartesianTransform.VERTICAL_AXIS).mapCache(_renderData.cache,"yValue","yNumber");
		// now convert
		if (_xField != "" && _sortOnXField)
			_renderData.cache.sortOn("xNumber",Array.NUMERIC);		

		super.updateMapping();

	}

	/**
	 *  @private
	 */
	override protected function updateFilter():void
	{
		_renderData.segments = [];
		var lineSegmentType:Class = this.lineSegmentType;
		if (filterData)
		{
			_renderData.filteredCache = _renderData.cache.concat();
			
			dataTransform.getAxis(CartesianTransform.HORIZONTAL_AXIS).filterCache(_renderData.filteredCache,"xNumber","xFilter")
			dataTransform.getAxis(CartesianTransform.VERTICAL_AXIS).filterCache(_renderData.filteredCache,"yNumber","yFilter");

			// since all nulls will end up out at the edges, we can safely strip this early

			if (xField != "")
				stripNaNs(_renderData.filteredCache,"xFilter");
				
			_renderData.validPoints = _renderData.filteredCache.length;

			if (_interpolateValues == false)
			{
				var start:int;
				var end:int = -1;
				var len:int = _renderData.filteredCache.length;
				var i:int;
				var v:LineSeriesItem;
				
				while (end<len)
				{
					for (i = end + 1; i < len; i++)
					{
						v = LineSeriesItem(_renderData.filteredCache[i]);
						if (!isNaN(v.xFilter) && !isNaN(v.yFilter))
							break;
						_renderData.validPoints--;
					}
					if (i == len)
						break;				

					start = i;

					for (i = start + 1; i < len; i++)
					{
						v = LineSeriesItem(_renderData.filteredCache[i]);
						if (isNaN(v.xFilter) || isNaN(v.yFilter))
							break;					
					}
					end = i-1;
					if (end != start)
					{
						_renderData.segments.push(new lineSegmentType(this,_renderData.segments.length,_renderData.filteredCache,start,end));
					}
				}
			}
			else
			{
				stripNaNs(_renderData.filteredCache,"yFilter");
				_renderData.validPoints = _renderData.filteredCache.length;
				if (_renderData.filteredCache.length > 0)
					_renderData.segments.push(new lineSegmentType(this,0,_renderData.filteredCache,start,_renderData.filteredCache.length-1));
			}
		}
		else
		{
			_renderData.filteredCache = _renderData.cache;
			_renderData.segments.push(new lineSegmentType(this,0,_renderData.filteredCache,start,_renderData.filteredCache.length-1));
		}

		super.updateFilter();
	}
	
	/**
	 *  @private
	 */
	override protected function updateTransform():void
	{
		dataTransform.transformCache(_renderData.filteredCache,"xNumber","x","yNumber","y");
		super.updateTransform();
	}

	/**
	 *  @private
	 */
	override public function describeData(dimension:String,
										  requiredFields:uint):Array
	{
		validateData();

		if (_renderData.cache.length == 0)
			return [];
			
		var description:DataDescription = new DataDescription();
		description.boundedValues = null;

		var dataMargin:Number;
		var stroke:IStroke;
		var radius:Number;
		var renderer:Object;

		if (dimension == CartesianTransform.VERTICAL_AXIS)
		{
			extractMinMax(_renderData.cache,"yNumber",description);
			if ((requiredFields & DataDescription.REQUIRED_BOUNDED_VALUES) != 0)
			{
				dataMargin = 0;
				
				stroke = getStyle("lineStroke");
				if (stroke != null)
					dataMargin = stroke.weight/2;
				
				radius = getStyle("radius");
				renderer = getStyle("itemRenderer");
				if (radius > 0 && renderer != null)
				{
					stroke = getStyle("stroke");
					if (stroke != null)
						radius += stroke.weight/2;
						
					dataMargin = Math.max(radius,dataMargin);
				}

				if (dataMargin > 0)
				{
					description.boundedValues= [];
					description.boundedValues.push(new BoundedValue(description.max,0,dataMargin));
					description.boundedValues.push(new BoundedValue(description.min,dataMargin,0));
				}
			}
		}
		else if (dimension == CartesianTransform.HORIZONTAL_AXIS)
		{
			if (_xField != "")
			{
				if ((requiredFields & DataDescription.REQUIRED_MIN_INTERVAL) != 0)
				{
					// if we need to know the min interval, then we rely on the cache being in order. So we need to sort it if it
					// hasn't already been sorted
					var cache:Array = _renderData.cache;
					if (_sortOnXField == false)
					{
						cache = _renderData.cache.concat();
						cache.sortOn("xNumber",Array.NUMERIC);		
					}
					extractMinMax(cache,"xNumber",description,(0 != (requiredFields & DataDescription.REQUIRED_MIN_INTERVAL)));
				}
				else
				{
					extractMinMax(_renderData.cache,"xNumber",description, (requiredFields & DataDescription.REQUIRED_MIN_INTERVAL) != 0);
				}
			}
			else 
			{
				description.min = _renderData.cache[0].xNumber;
				description.max = _renderData.cache[_renderData.cache.length-1].xNumber;
				if ((requiredFields & DataDescription.REQUIRED_MIN_INTERVAL) != 0)
				{
					extractMinInterval(_renderData.cache,"xNumber",description);
				}
			}

			if ((requiredFields & DataDescription.REQUIRED_BOUNDED_VALUES) != 0)
			{
				dataMargin = 0;
				
				stroke = getStyle("lineStroke");
				if (stroke != null)
					dataMargin = stroke.weight/2;
				
				radius = getStyle("radius");
				renderer = getStyle("itemRenderer");
				if (radius > 0 && renderer != null)
				{
					stroke = getStyle("stroke");
					if (stroke != null)
						radius += stroke.weight/2;
						
					dataMargin = Math.max(radius,dataMargin);
				}

				if (dataMargin > 0)
				{
					description.boundedValues= [];
					description.boundedValues.push(new BoundedValue(description.max,0,dataMargin));
					description.boundedValues.push(new BoundedValue(description.min,dataMargin,0));
				}
			}
		}
		else
		{
			return [];
		}
			
		return [ description ];
	}

	/**
	 *  @private
	 */
	override public function findDataPoints(x:Number,y:Number,sensitivity:Number):Array
	{
		if (interactive == false)
			return [];

		var pr:Number = getStyle("radius");
		var minDist2:Number  = pr + sensitivity;
		minDist2 *= minDist2;
		var minItem:LineSeriesItem;		
		var pr2:Number = pr * pr;
		
		var len:int = _renderData.filteredCache.length;

		if (len == 0)
			return [];
			
		var low:Number = 0;
		var high:Number = len;
		var cur:Number = Math.floor((low+high)/2);

		var bFirstIsNaN:Boolean = isNaN(_renderData.filteredCache[0]);

		while (true)
		{
			var v:LineSeriesItem = _renderData.filteredCache[cur];			
			if (!isNaN(v.yFilter) && !isNaN(v.xFilter))
			{
				var dist:Number = (v.x  - x)*(v.x  - x) + (v.y - y)*(v.y -y);
				if (dist <= minDist2)
				{
					minDist2 = dist;
					minItem = v;				
				}
			}
			// if there are NaNs in this array, it's for one of a couple of reasons:
			// 1) there were NaNs in the data, which menas an xField was provided, which means they got sorted to the end
			// 2) some values got filtered out, in which case we can (sort of) safely assumed that the got filtered from one side, the other, or the entire thing.
			// we'll assume that an axis hasn't filtered a middle portion of the array.
			// since we can assume that any NaNs are at the beginning or the end, we'll rely on that in our binary search.  If there was a NaN in the first slot,
			// then we'll assume it's safe to move up the array if we encounter a NaN.  It's possible the entire array is NaN, but then nothing will match, so that's ok.
			if (v.x < x || (isNaN(v.x) && bFirstIsNaN))
			{
				low = cur;
				cur = Math.floor((low + high)/2);
				if (cur == low)
					break;
			}
			else
			{
				high = cur;
				cur = Math.floor((low + high)/2);
				if (cur == high)
					break;
			}
		}

		if (minItem != null)
		{
			var hd:HitData = new HitData(createDataID(minItem.index),Math.sqrt(minDist2),minItem.x,minItem.y,minItem);

			var istroke:IStroke = getStyle("lineStroke");
			if (istroke is Stroke)
				hd.contextColor = Stroke(istroke).color;
			else if (istroke is LinearGradientStroke)
			{
				var gb:LinearGradientStroke = LinearGradientStroke(istroke);
				if (gb.entries.length > 0)
					hd.contextColor = gb.entries[0].color;
			}
			hd.dataTipFunction = formatDataTip;
			return [ hd ];
		}

		return [];
	}


	/**
	 *  @private
	 */
	override public function getElementBounds(renderData:Object):void
	{
		var cache :Array = renderData.cache;
		var segments:Array = renderData.segments;
		
		var rb :Array = [];
		var sampleCount:int = cache.length;		

		if (sampleCount == 0)
			maxBounds  = new Rectangle();
		else
		{
			var radius:Number = renderData.radius;

			if (radius == 0 || isNaN(radius))
				radius = 1;

			var segCount:int = segments.length;

			var v:Object = cache[renderData.segments[0].start];
			var maxBounds:Rectangle = new Rectangle(v.x,v.y,0,0);



			for (var j:int=0;j<segCount;j++)
			{			
				var seg:Object = renderData.segments[j];
				for (var i:int = seg.start; i <= seg.end; i++)
				{
					v = cache[i];
					var b:Rectangle = new Rectangle(v.x-radius,v.y-radius,2*radius,2*radius);

					maxBounds.left = Math.min(maxBounds.left,b.left);
					maxBounds.top = Math.min(maxBounds.top,b.top);
					maxBounds.right = Math.max(maxBounds.right,b.right);
					maxBounds.bottom = Math.max(maxBounds.bottom,b.bottom);
					rb[i] = b;
				}
			}
		}
		

		renderData.elementBounds = rb;
		renderData.bounds =  maxBounds;
	}

	/**
	 *  @private
	 */
	override public function beginInterpolation(sourceRenderData:Object,
												destRenderData:Object):Object
	{
		var idata:Object = initializeInterpolationData(
			sourceRenderData.cache, destRenderData.cache,
			{ x: true, y: true }, itemType,
			{ sourceRenderData: sourceRenderData,
			  destRenderData: destRenderData });

		var interpolationRenderData:LineSeriesRenderData = LineSeriesRenderData(destRenderData.clone());

		interpolationRenderData.cache = idata.cache;	
		interpolationRenderData.filteredCache = idata.cache;	

		/* the segments in the renderdata have pointers back to the filetered cache.  since we just replaced the filtered cache, we need to iterate through and 
		/  update those */
		var segs:Array = interpolationRenderData.segments;
		var len:int = segs.length;
		for (var i:int = 0; i < len; i++)
		{
			segs[i].items = idata.cache;
		}
		
		transitionRenderData = interpolationRenderData;
		return idata;
	}

	/**
	 *  @private
	 */
	override protected function getMissingInterpolationValues(
									sourceProps:Object, srcCache:Array,
									destProps:Object, destCache:Array,
									index:Number, customData:Object):void
	{
		var cache:Array = customData.sourceRenderData.cache;
		var dstCache:Array = customData.destRenderData.cache;
		
		for (var propName:String in sourceProps)
		{
			var src:Number = sourceProps[propName];
			var dst:Number = destProps[propName];


			var lastValidIndex:int = index;
			if (isNaN(src))
			{
				if (cache.length == 0)
				{
					src = (propName == "x")? dstCache[index].x : unscaledHeight;
				}
				else
				{
					if (lastValidIndex >= cache.length)
						lastValidIndex = cache.length-1;
					while (lastValidIndex >= 0 && isNaN(cache[lastValidIndex][propName]))
					{
						lastValidIndex--;
					}
					if (lastValidIndex >= 0)
						src =   cache[lastValidIndex][propName] + .01 * (lastValidIndex - index);
					if (isNaN(src))
					{
						lastValidIndex = index+1;
						var cachelen:int = cache.length;
						while (lastValidIndex < cachelen && isNaN(cache[lastValidIndex][propName]))
						{
							lastValidIndex++;
						}
						if (lastValidIndex < cachelen)
						{
							src = cache[lastValidIndex][propName] + .01 * (lastValidIndex - index);
						}
					}			
				}
			}
			
			sourceProps[propName] = src;
			destProps[propName] = dst;
		}
	}

	//--------------------------------------------------------------------------
	//
	//  Methods
	//
	//--------------------------------------------------------------------------

	/**
	 *  Customizes the item renderer instances that are used to represent the chart. This method is called automatically
	 *	whenever a new item renderer is needed while the chart is being rendered. You can override this method to add your own customization as necessary.
	 *	@param	instance	The new item renderer instance that is being created.
	 *	@param	cache		The InstanceCache that is used to manage the item renderer instances.
	 */
	protected function applyItemRendererProperties(instance:DisplayObject,
												   cache:InstanceCache):void
	{
		if (instance is ISimpleStyleClient)
			ISimpleStyleClient(instance).styleName = this;
	}

	/**
	 *  @private
	 */
	private function formatDataTip(hd:HitData):String
	{
		var dt:String = "";
		
		var n:String = displayName;
		if (n && n != "")
			dt += "<b>" + n + "</b><BR/>";

		var xName:String = dataTransform.getAxis(CartesianTransform.HORIZONTAL_AXIS).displayName;
		if (xName != "")
			dt += "<i>" + xName+ ":</i> ";
		dt += dataTransform.getAxis(CartesianTransform.HORIZONTAL_AXIS).formatForScreen(
			LineSeriesItem(hd.chartItem).xValue) + "\n";
		
		var yName:String = dataTransform.getAxis(CartesianTransform.VERTICAL_AXIS).displayName;
		if (yName != "")
			dt += "<i>" + yName + ":</i> ";
		dt += dataTransform.getAxis(CartesianTransform.VERTICAL_AXIS).formatForScreen(
			LineSeriesItem(hd.chartItem).yValue) + "\n";
		
		return dt;
	}
}

}

////////////////////////////////////////////////////////////////////////////////
//
//  Helper class: LineSeriesLegendMarker
//
////////////////////////////////////////////////////////////////////////////////


import flash.display.Graphics;
import mx.charts.series.LineSeries;
import mx.graphics.IStroke;
import mx.graphics.Stroke;
import mx.graphics.LinearGradientStroke;
import mx.skins.ProgrammaticSkin;

/**
 *  @private
 */
class LineSeriesLegendMarker extends ProgrammaticSkin
{
	//--------------------------------------------------------------------------
	//
	//  Constructor
	//
	//--------------------------------------------------------------------------

	/**
	 *  @private
	 *  Constructor.
	 */
	public function LineSeriesLegendMarker(element:LineSeries)
	{
		super();

		_element = element;
		styleName = _element;
	}

	//--------------------------------------------------------------------------
	//
	//  Variables
	//
	//--------------------------------------------------------------------------

	/**
	 *  @private
	 */
	private var _element:LineSeries;

	//--------------------------------------------------------------------------
	//
	//  Overridden methods
	//
	//--------------------------------------------------------------------------

	/**
	 *  @private
	 */
	override protected function updateDisplayList(unscaledWidth:Number,
												  unscaledHeight:Number):void
	{
		super.updateDisplayList(unscaledWidth,unscaledHeight);

		var fillStroke:IStroke = getStyle("lineStroke");
		var color:Number;

		if (fillStroke is Stroke)
		{
			color = Stroke(fillStroke).color;
		}
		else if (fillStroke is LinearGradientStroke)
		{
			var gb:LinearGradientStroke = LinearGradientStroke(fillStroke);
			if (gb.entries.length > 0)
				color = gb.entries[0].color;
		}

		var g:Graphics = graphics;
		g.clear();
		g.moveTo(0, 0);
		g.lineStyle(0, 0, 0);
		g.beginFill(color);
		g.lineTo(width, 0);
		g.lineTo(width, height);
		g.lineTo(0, height);
		g.lineTo(0, 0);
		g.endFill();
	}
}
